/*
 * Copyright 2008 Johnny Jian
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.getris.panel

import com.googlecode.getris.game.color.ColorfulContainer
import com.googlecode.getris.game.score.HighScoreManager
import com.googlecode.getris.game.score.ScoreKeeper
import static com.googlecode.getris.panel.GameState.*
import java.awt.Color
import java.awt.Font
import java.awt.Graphics
import java.awt.event.ActionListener
import java.awt.event.KeyAdapter
import static java.awt.event.KeyEvent.*
import javax.swing.Timer

class TetrisPanel extends SquarePanel {

	private static final NOTICE_WIDTH = 180
	private static final NOTICE_HEIGHT = 50
	private static final NOTICE_FONT = new Font('SansSerif', Font.BOLD, 26)
	private static final NOTICE_COLOR = [240, 240, 240] as Color

	def changingObservable
	def gameOverObservable
	def queueSize
	def container
	def scoreKeeper
	def highScores
	def timer
	def state

	TetrisPanel(width, height, size, queue, top) {
		super(width, height, size)
		queueSize = queue
		state = STOPPED
		changingObservable = new Observable()
		gameOverObservable = new Observable()
		timer = new DropPaceTimer(this, dropPace)
		highScores = new HighScoreManager(top: top)

		focusable = true
		addKeyListener([keyPressed: keyPressed] as KeyAdapter)
	}

// this method is removed because of separating changing observers and game over observers
//	def methodMissing(String name, args) {
//		Observable.metaClass.invokeMethod(changingObservable, name, args)
//	}

	def addChangingObserver(observer) {
		changingObservable.addObserver(observer)
	}

	def addGameOverObserver(observer) {
		gameOverObservable.addObserver(observer)
	}

	def moveLeft = {
		container.droppingQuadrel.moveLeft()
	}

	def moveRight = {
		container.droppingQuadrel.moveRight()
	}

	def rotate = {
		container.droppingQuadrel.rotate()
	}

	def dropPace = {
		if (!running) {
			return
		}
		container.droppingQuadrel.dropPace()
		if (container.droppingQuadrel.reachedBottom) {
			setNextScene()
		}
	}

	def dropDown = {
		container.droppingQuadrel.dropDown()
		setNextScene()
	}

	def keyMap = [
	    (VK_LEFT): moveLeft,
		(VK_RIGHT): moveRight,
		(VK_UP): rotate,
		(VK_DOWN): dropPace,
		(VK_SPACE): dropDown,

		(VK_A): moveLeft,
		(VK_D): moveRight,
		(VK_J): rotate,
		(VK_S): dropPace,
		(VK_K): dropDown,
	]

	def keyPressed = { e ->
		if (!running) {
			return
		}
		if (keyMap.containsKey(e.keyCode)) {
			keyMap[e.keyCode]()
		}
	}

	boolean isStopped() {
		state == STOPPED
	}

	boolean isRunning() {
		state == RUNNING
	}

	boolean isPaused() {
		state == PAUSED
	}

	private setNextScene() {
		container.removeFullLines()
		scoreKeeper.addLines(container.removedLineCount)
		container.dropNextQuadrel()

		if (container.gameOver) {
			state = STOPPED
			repaint()
		}

		// notifying observers must after checking if game is over, or the state of game will be incorrect
		notifyObservers()
	}

	private notifyObservers() {
		// changing observers will be notified first, and then the game over observers will be (if the game is over)
		changingObservable.setChanged()
		changingObservable.notifyObservers(this)

		if (container.gameOver) {
			gameOverObservable.setChanged()
			gameOverObservable.notifyObservers(this)
		}
	}

	def newGame() {
		container = new ColorfulContainer(containerWidth, containerHeight, queueSize)
		scoreKeeper = new ScoreKeeper()
		colors = new Color[containerHeight][containerWidth]

		container.addObserver({ o, arg -> drawDifferentSquares() } as Observer)
		container.startToDrop()
		state = RUNNING
		notifyObservers()

		repaint()
		requestFocus()
	}

	def resume() {
		state = RUNNING
		timer.restart()
		repaint()
		requestFocus()
	}

	def pause() {
		state = PAUSED
		timer.stop()
		repaint()
	}

	private drawDifferentSquares() {
		colors?.eachWithIndex { row, y ->
			row.eachWithIndex { c, x ->
				def color = container.getColorAt(x, y)
				if (c != color) {
					colors[y][x] = color
					drawSquareAt x, y
				}
			}
		}
	}

	private drawNotice(text, color, g) {
		g.font = NOTICE_FONT

		def noticeLeft = (width - NOTICE_WIDTH).intdiv(2)
		def noticeTop = (height - NOTICE_HEIGHT).intdiv(2) - 50
		def metrics = g.fontMetrics
		def textWidth = metrics.stringWidth(text)
		def textHeight = metrics.ascent + metrics.descent
		def textLeft = (NOTICE_WIDTH - textWidth).intdiv(2) + noticeLeft
		def textTop = (NOTICE_HEIGHT - textHeight).intdiv(2) + noticeTop + metrics.ascent

		g.color = NOTICE_COLOR
		g.fill3DRect noticeLeft, noticeTop, NOTICE_WIDTH, NOTICE_HEIGHT, true
		g.color = color
		g.drawString text, textLeft, textTop
	}

	void paint(Graphics g) {
		super.paint(g)
		if (paused) {
			drawNotice('PAUSED', Color.BLUE, g)
		}
		if (container?.gameOver) {
			drawNotice('GAME OVER', Color.RED, g)
		}
	}

}

enum GameState { STOPPED, RUNNING, PAUSED }

class DropPaceTimer {

	private static final INIT_DELAY = 1000
	private static final DELAY_REDUCE_FACTOR = 0.9

	def action
	def timer
	def level
	def delay

	DropPaceTimer(tetris, action) {
		this.action = action
		level = 0

		// when a new quadrel is coming, restart the timer
		tetris.addChangingObserver({ o, game ->
			def scoreKeeper = game.scoreKeeper
			if (level != scoreKeeper.level) { // if level up, update the delay
				level = scoreKeeper.level
				delay = (INIT_DELAY * DELAY_REDUCE_FACTOR ** (level - 1)) as int
			}
			restart()
		} as Observer)

		// stop the timer when the game is over
		tetris.addGameOverObserver({ o, game ->
			level = 0
			stop()
		} as Observer)
	}

	def restart() {
		stop()
		timer = new Timer(delay, action as ActionListener)
		timer.start()
	}

	def stop() {
		timer?.stop()
	}

}
